# Exceptions.java

    package org.codefilarete.tool.exception;

    import java.util.NoSuchElementException;
    import java.util.function.Predicate;

    import org.codefilarete.tool.collection.Iterables;
    import org.codefilarete.tool.collection.ReadOnlyIterator;

    /**
     *
     * @author Guillaume Mary
     */
    public interface Exceptions {

    	/**
    	 * Convert a {@link Throwable} into a {@link RuntimeException}. Do nothing if the {@link Throwable} is already a {@link RuntimeException},
    	 * else instantiate a {@link RuntimeException} with the exception as the init cause.
    	 * <b>Please use with caution because doing this can be considered as a bad practice.</b>
    	 *
    	 * @param t any kinf of exception
    	 * @return the {@link Throwable} itself if it's already a {@link RuntimeException},
    	 * 			else a {@link RuntimeException} which cause is the {@link Throwable} argument
    	 */
    	static RuntimeException asRuntimeException(Throwable t) {
    		if (t instanceof RuntimeException) {
    			return  (RuntimeException) t;
    		} else {
    			return new RuntimeException(t);
    		}
    	}

    	static <T> T findExceptionInCauses(Throwable t, Class<T> throwableClass) {
    		return (T) findExceptionInCauses(t, new ClassExceptionFilter<>(throwableClass));
    	}

    	static <T> T findExceptionInCauses(Throwable t, Class<T> throwableClass, String message) {
    		return (T) findExceptionInCauses(t, new ClassAndMessageExceptionFilter<>(throwableClass, message));
    	}

    	static <T> T findExceptionInCauses(Throwable t, Class<T> throwableClass, Predicate<String> messageAccepter) {
    		return (T) findExceptionInCauses(t, new ClassAndMessageExceptionFilter<>(throwableClass, messageAccepter));
    	}

    	/**
    	 * Look up a {@link Throwable} in the causes hierarchy of the {@link Throwable} argument according to a {@link ExceptionFilter}
    	 *
    	 * @param t the initial stack error
    	 * @param filter a filter
    	 * @return null if not found
    	 */
    	static Throwable findExceptionInCauses(Throwable t, final ExceptionFilter filter) {
    		return Iterables.stream(new ExceptionCauseIterator(t)).filter(filter::accept).findAny().orElse(null);
    	}

    	/**
    	 * Iterator on {@link Throwable} causes (and itself)
    	 */
    	class ExceptionCauseIterator extends ReadOnlyIterator<Throwable> {

    		private Throwable currentThrowable;

    		private boolean hasNext = false;

    		public ExceptionCauseIterator(Throwable throwable) {
    			this.currentThrowable = throwable;
    		}

    		@Override
    		public boolean hasNext() {
    			return hasNext = currentThrowable != null;
    		}

    		@Override
    		public Throwable next() {
    			if (!hasNext) {
    				// this is necessary to be compliant with Iterator#next(..) contract
    				throw new NoSuchElementException();
    			}
    			Throwable next = currentThrowable;
    			currentThrowable = currentThrowable.getCause();
    			return next;
    		}
    	}

    	interface ExceptionFilter {
    		boolean accept(Throwable t);
    	}

    	class ClassExceptionFilter<T> implements ExceptionFilter {

    		private final Class<T> targetClass;

    		public ClassExceptionFilter(Class<T> c) {
    			this.targetClass = c;
    		}

    		public boolean accept(Throwable t) {
    			return targetClass.isAssignableFrom(t.getClass());
    		}
    	}

    	class ClassAndMessageExceptionFilter<T> extends ClassExceptionFilter<T> {

    		private final Predicate<String> messageAccepter;

    		public ClassAndMessageExceptionFilter(Class<T> c, String message) {
    			super(c);
    			this.messageAccepter = message::equalsIgnoreCase;
    		}

    		public ClassAndMessageExceptionFilter(Class<T> c, Predicate<String> messageAccepter) {
    			super(c);
    			this.messageAccepter = messageAccepter;
    		}

    		@Override
    		public boolean accept(Throwable t) {
    			return super.accept(t) && this.messageAccepter.test(t.getMessage());
    		}
    	}

    }